﻿using Daemon.Common;
using Daemon.Data.Substructure.Const;
using Daemon.Model;
using Daemon.Repository.Contract;
using Daemon.Service.Contract;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Daemon.Service
{
	public class UserAuthorizationService : IUserAuthorizationService
	{
		private const int DEFAULT_MAX_LOGIN_FAILED_TIMES = 3;
		private readonly IConfiguration _configuration;
		private readonly IAdminUserRepository _adminUserRepository;
		private readonly IUserLogRepository _userLogRepository;

		private readonly IUserRoleRepository _userRoleRepository;

		private readonly IAdminRoleRepository _adminRoleRepository;

		public UserAuthorizationService(IAdminRoleRepository adminRoleRepository, IUserRoleRepository userRoleRepository, IAdminUserRepository adminUserRepository, IUserLogRepository userLogRepository, IConfiguration configuration)
		{
			_configuration = configuration;
			_adminUserRepository = adminUserRepository;
			_userLogRepository = userLogRepository;
			_userRoleRepository = userRoleRepository;
			_adminRoleRepository = adminRoleRepository;
		}

		public int GetMaxLoginFailedTimes()
		{
			var maxLoginFailedTimes = Convert.ToInt32(_configuration[""]?.ToString() ?? "0");
			maxLoginFailedTimes = (maxLoginFailedTimes != 0) ? maxLoginFailedTimes : DEFAULT_MAX_LOGIN_FAILED_TIMES;

			if (maxLoginFailedTimes <= 0)
			{
				maxLoginFailedTimes = DEFAULT_MAX_LOGIN_FAILED_TIMES;
			}

			return maxLoginFailedTimes;
		}

		public AdminUser GetUserEntity(string username, List<int> roleIds = null, string userType = null)
		{
			return CreateUserEntityWithNameOrEmailAndRoles(username, roleIds, userType);
		}

		public AdminUser GetUserEntityByEmail(string email, List<int> roleIds = null, string userType = null)
		{
			return CreateUserEntityWithNameOrEmailAndRoles(email, roleIds, userType);
		}

		public AdminUser GetUserEntity(int userId, List<int> roleIds = null, string userType = null)
		{
			var userName = _adminUserRepository.FindAll().FirstOrDefault(r => r.Id == userId)?.UserName;
			if (string.IsNullOrEmpty(userName))
			{
				return null;
			}

			return GetUserEntity(userName, roleIds, userType);
		}

		public AdminUser GetUserEntity(string name, string password, out string error)
		{
			return UserValidate(name, password, out error);
		}

		public AdminUser GetUserEntityByEmail(string email, out string error)
		{
			return UserValidate(email, out error);
		}

		public AdminUser UserValidate(string email, out string error)
		{
			error = MessageConst.INVALID_CLIENT_USERNAME_PW;
			var user = _adminUserRepository.FindAll().FirstOrDefault(r => r.Email == email);
			if (user == null || user.Password == null)
			{
				return null;
			}
			var userLog = _userLogRepository.FindById(user.Id) ?? new UserLog { UserID = user.Id };
			var loginFailedTimes = userLog.NumCount;
			var lastLoginFailed = userLog.LastAttempt;
			var maxLoginFailedTimes = GetMaxLoginFailedTimes();
			var lockoutMsg = $"你的账户已经被锁定因为你3次登录失败，请联系你的管理员！";
			if (lastLoginFailed.Date < DateTime.Now.Date)
			{
				loginFailedTimes = 0;
			}

			if (loginFailedTimes >= maxLoginFailedTimes)
			{
				error = lockoutMsg;
				return null;
			}

			var userWithPassword = GetUserEntityByEmail(email);
			if (userWithPassword == null)
			{
				loginFailedTimes += 1;
				userLog.NumCount = loginFailedTimes;
				userLog.LastAttempt = DateTime.Now;
				_userLogRepository.AddOrUpdate(userLog);
				if (loginFailedTimes >= maxLoginFailedTimes)
				{
					error = lockoutMsg;
				}
				else
				{
					error = $"你已经登录失败{loginFailedTimes}次" + $". 你的账户将被锁定在{maxLoginFailedTimes}次登录失败以后.";
				}

				return null;
			}

			if (userLog.Id > 0)
			{
				_userLogRepository.Delete(userLog);
			}

			error = null;
			return userWithPassword;
		}

		public AdminUser UserValidate(string name, string password, out string error)
		{
			error = MessageConst.INVALID_CLIENT_USERNAME_PW;
			var user = _adminUserRepository.FindAll().FirstOrDefault(r => r.UserName == name);
			if (user == null || user.Password == null)
			{
				return null;
			}
			var userLog = _userLogRepository.FindById(user.Id) ?? new UserLog { UserID = user.Id };
			var loginFailedTimes = userLog.NumCount;
			var lastLoginFailed = userLog.LastAttempt;
			var maxLoginFailedTimes = GetMaxLoginFailedTimes();
			var lockoutMsg = $"你的账户已经被锁定因为你3次登录失败，请联系你的管理员！";
			if (lastLoginFailed.Date < DateTime.Now.Date)
			{
				loginFailedTimes = 0;
			}

			if (loginFailedTimes >= maxLoginFailedTimes)
			{
				error = lockoutMsg;
				return null;
			}

			var userWithPassword = GetUserEntity(name, password);
			if (userWithPassword == null)
			{
				loginFailedTimes += 1;
				userLog.NumCount = loginFailedTimes;
				userLog.LastAttempt = DateTime.Now;
				_userLogRepository.AddOrUpdate(userLog);
				if (loginFailedTimes >= maxLoginFailedTimes)
				{
					error = lockoutMsg;
				}
				else
				{
					error = $"你已经登录失败{loginFailedTimes}次" + $". 你的账户将被锁定在{maxLoginFailedTimes}次登录失败以后.";
				}

				return null;
			}

			if (userLog.Id > 0)
			{
				_userLogRepository.Delete(userLog);
			}

			error = null;
			return userWithPassword;
		}

		private AdminUser CreateUserEntityWithNameOrEmailAndRoles(string nameOrEmail, List<int> addRoleIds = null, string userType = null)
		{
			var user = _adminUserRepository.FindAll().FirstOrDefault(r => r.UserName == nameOrEmail || r.Email == nameOrEmail);
			return CreateUserEntityWithNameAndRoles(user, addRoleIds, userType);
		}

		private AdminUser CreateUserEntityWithNameAndRoles(AdminUser user, List<int> addRoleIds = null, string userType = null)
		{
			if (user != null)
			{
				user.UserType = userType;
			}

			List<UserRole> userRoles = new List<UserRole>();
			userRoles = _userRoleRepository.FindAll().Include(r => r.AdminRole).Where(r => r.UserId == user.Id).ToList();

			List<int> roleIds = new List<int>();
			roleIds = _userRoleRepository.FindAll().Where(r => r.UserId == user.Id).Select(r => r.Id).ToList();
			var existRoleIds = userRoles.Select(r => r.RoleId).ToArray();
			roleIds = roleIds.Except(existRoleIds).ToList();
			if (roleIds.Any())
			{
				var roleData = _adminRoleRepository.FindAll().Where(r => roleIds.Contains(r.Id)).ToList();
				userRoles.AddRange(roleData.Select(r => new UserRole() { RoleId = r.Id, AdminRole = r, UserId = user.Id }));
			}

			var isAdmin = roleIds.Contains(SystemConst.ADMINISTRATOR_ROLE_ID);
			if (!userRoles.Any())
			{
				return user;
			}

			user.UserRoles = userRoles;
			user.Roles = userRoles.Select(r => r.AdminRole).ToList();
			user.IsAdmin = isAdmin || user.IsAdmin;
			return user;
		}

		private AdminUser GetUserEntity(string username, string password)
		{
			var adminUser = _adminUserRepository.FindAll().FirstOrDefault(r => r.UserName == username);
			var userRoles = _userRoleRepository.FindAll().Include("AdminRole").Where(r => r.UserId == adminUser.Id).ToList();
			adminUser.UserRoles = userRoles;
			if (adminUser?.Password != null && string.Equals(PasswordHelper.Rfc2898Decrypt(adminUser?.Password, "daemon"), password))
			{
				adminUser.UserType = "Security";
				return adminUser;
			}

			return null;
		}
	}
}

